package com.example.micro.thread.counter;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 如果Thread被回收了，对应的计数就丢失了，
 * 所以需要自己实现一下存储计数使用的map。
 */
public class ThradeLocalCount2 {
    static class Counter {
        private static class Entry {
            long q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe;

            long count = 0;
        }

        //存放销毁线程的计算
        private volatile long removedTotal = 0;

        private final Lock lock = new ReentrantLock();
        private final Map<WeakReference<Thread>, Entry> map = new HashMap<>();

        //存放销毁线程的软引用，并非销毁线程对象本身，
        //引用关系如下
        //栈中的WeakReference<Thread> -> 堆中的WeakReference<Thread> -> Thread
        private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

        private final ThreadLocal<Entry> local = ThreadLocal.withInitial(Entry::new);

        public void increase() {
            Entry entry = local.get();
            long count = entry.count;
            if (count == 0) {
                lock.lock();
                try {
                    //new WeakReference<>(Thread.currentThread(), queue)
                    //在对象被回收后，会把弱引用对象，也就是WeakReference对象或者其子类的对象，
                    //放入队列ReferenceQueue中，注意不是被弱引用的对象，被弱引用的对象已经被回收了。
                    map.put(new WeakReference<>(Thread.currentThread(), queue), entry);
                } finally {
                    lock.unlock();
                }
            }
            expunge();
            entry.count = count + 1;
        }

        /**
         * 遍历软引用中的queue，把销毁的线程中计算好的数值加起来
         * 存放到 removedTotal
         */
        private void expunge() {
            for (Object destroyObj; (destroyObj = queue.poll()) != null; ) {
                synchronized (queue) {
                    lock.lock();
                    try {
                        Entry e = map.get(destroyObj);
                        map.remove(destroyObj);
                        removedTotal += e.count;
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }

        /**
         * 计算总数
         *
         * @return
         */
        public long getAll() {
            return map.values().stream().map(entry -> entry.count).reduce(0L, Long::sum) + removedTotal;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        int number = 4;
        Thread[] threads = new Thread[number];
        for (int i = 0; i < number; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100_000_000; j++) {
                    counter.increase();
                }
            });
        }
        for (Thread thread1 : threads) {
            thread1.start();
        }
        for (Thread thread2 : threads) {
            thread2.join();
        }
        for (int i = 0; i < number; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100_000_000; j++) {
                    counter.increase();
                }
            });
        }
        long start = System.currentTimeMillis();
        for (Thread thread1 : threads) {
            thread1.start();
        }
        for (Thread thread2 : threads) {
            thread2.join();
        }
        System.out.println(System.currentTimeMillis() - start);
        System.out.println(counter.getAll());

        start = System.currentTimeMillis();
        for (long i = 0; i < 800_000_000L; i++) {
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}